package com.basker.pisces.core.query.factory;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import com.basker.pisces.core.query.builder.CriterionBuilder;
import com.basker.pisces.core.query.builder.CriterionBuilder.Operator;
import com.basker.pisces.utils.ListsUtils;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

/**
 * 表示条件的POJO,可以是一个单字段条件或是复合条件，如果children有元素，那么就是后者。 显然，field、operator、value 与
 * children、logicRelation是互斥的
 *
 * @author hangwen
 */
@Getter
@Setter
@ToString
public class Condition {

    public static final String AND = "AND";
    public static final String OR = "OR";

    /**
     * 如果当前条件是复合条件，连接这些条件的逻辑符号
     */
    @Deprecated
    private String logicRelation = AND;

    /**
     * 如果当前条件是复合条件，children里添加子条件
     * <p>
     * 推荐使用{@link #and}或者{@link #or}
     */
    @Deprecated
    private List<Condition> children = new ArrayList<>();

    /**
     * 多个条件以and拼接
     */
    private List<Condition> and = new ArrayList<>();

    /**
     * 多个条件以or拼接
     */
    private List<Condition> or = new ArrayList<>();

    /**
     * 条件对应的字段（属性）
     */
    private String field;

    /**
     * 条件的符号,{@link Operator}中定义的符号都支持
     *
     * @see CriterionBuilder#create(String, String, Object)
     */
    private String operator;

    /**
     * 条件值，需要和符号匹配（当然框架会尽量契合）
     */
    private Object value;

    public Condition() {

    }

    public Condition(String andOr, Collection<Condition> children) {
        this.setLogicRelation(andOr);

        Assert.notEmpty(children, "parameter 'children' is required");
        this.children.addAll(children);
    }

    public Condition(String field, String operator, Object value) {
        Assert.hasLength(field, "parameter 'field' is required");
        Assert.hasLength(operator, "parameter 'operator' is required");

        this.field = field;
        this.operator = operator;
        this.value = value;
    }

    public void addCondition(Condition... conditions) {
        if (conditions.length == 1) {
            this.children.add(conditions[0]);
        } else {
            this.children.addAll(Arrays.asList(conditions));
        }
    }

    public CriterionBuilder createCriterionBuilder() {
        //TODO 需要check以排除互斥的属性
        if (this.isSingle()) {
            // 单字段条件
            return CriterionBuilder.create(this.getField(), this.getOperator(), this.getValue());
        } else if (!CollectionUtils.isEmpty(children)) {
            // children property is Deprecated
            List<CriterionBuilder> conditions = this.toCriteriaBuilders(children);
            if (CollectionUtils.isEmpty(conditions)) {
                return null;
            }

            if (this.logicRelation.equals(AND)) {
                return CriterionBuilder.and(conditions);
            } else {
                return CriterionBuilder.or(conditions);
            }
        } else if (!CollectionUtils.isEmpty(and)) {
            List<CriterionBuilder> conditions = this.toCriteriaBuilders(and);
            if (CollectionUtils.isEmpty(conditions)) {
                return null;
            }

            return CriterionBuilder.and(conditions);
        } else {
            List<CriterionBuilder> conditions = this.toCriteriaBuilders(or);
            if (CollectionUtils.isEmpty(conditions)) {
                return null;
            }

            return CriterionBuilder.or(conditions);
        }
    }

    public void setLogicRelation(String logicRelation) {
        Assert.hasText(logicRelation, "parameter 'logicRelation' is required");
        this.logicRelation = logicRelation.toUpperCase();

        Assert.isTrue(AND.equals(this.logicRelation) || OR.equals(this.logicRelation),
                "parameter 'logicRelation' is illegal");
    }

    private boolean isSingle() {
        return CollectionUtils.isEmpty(children)
                && CollectionUtils.isEmpty(and)
                && CollectionUtils.isEmpty(or);
    }

    private List<CriterionBuilder> toCriteriaBuilders(List<Condition> conditions) {
        return ListsUtils.mapNotNull(conditions, Condition::createCriterionBuilder);
    }

}
